package com.wheel.core.utils;/*
 * <h3>Title:wheel-spring-boot-starter-logging系统</h3>
 * <p>Copyright (c) 2020, wangnaifei@163.com. All rights reserved.</p>
 * @author wangnaifei@163.com
 */

import java.util.Calendar;

/**
 * <dt>Description:SnowFlakeID</dt>
 * <dd>分布式ID，Twitter雪花算法</dd>
 * <dd>类似功能使用雪花算法的还有百度，美团，滴滴</dd>
 * 首位不用┌─────────────────41位时间戳──────────────────┐            ┌─12bit序列号─┐
 *       0-00000000 00000000 00000000 00000000 00000000 0-00000000 00-00000000 0000
 *                                                       └10bit机器ID┘
 * @author wangnaifei@163.com
 * @version 1.0 <i>2020/11/20 11:41</i>
 * @since jdk1.7+
 */
public class SnowFlakeID {

    /**
     * 起始的时间戳，为了生成更小，更多ID，定义离当前最近的时间戳，定义后不能更改，更改会导致时钟偏斜
     */
    private static long START_TIMESTAMP = 61566537600000L;//20201120 timestamp

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12;   //序列号占用的位数
    private final static long MACHINE_BIT = 5;     //机器标识占用的位数
    private final static long DATA_CENTER_BIT = 5; //数据中心占用的位数

    /**
     * 每一部分的最大值
     */
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);            //-1L ^ (-1L << SEQUENCE_BIT)
    private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);          //-1L ^ (-1L << MACHINE_BIT)
    private final static long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);  //-1L ^ (-1L << DATA_CENTER_BIT)

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT; //41位的时间截，可以使用69年，年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69

    private long dataCenterId;          //数据中心
    private long machineId;             //机器标识
    private long sequence = 0L;         //序列号
    private long lastTimeStamp = -1L;   //上一次时间戳

    private long getNextMill() {
        long mill = getNewTimeStamp();
        while (mill <= lastTimeStamp) {
            mill = getNewTimeStamp();
        }
        return mill;
    }

    private long getNewTimeStamp() {
        return System.currentTimeMillis();
    }

    /**
     * <dt>Description:SnowFlakeID</dt>
     * <dd>根据指定的数据中心ID和机器标志ID生成指定的序列号</dd>
     * <dd>author: wangnaifei@163.com</dd>
     * <dd>version:1.0 <i>2020/11/20 14:34</i></dd>
     *
     * @param dataCenterId 数据中心ID
     * @param machineId    机器标志ID
     * @since jdk1.7+
     */
    public SnowFlakeID(long dataCenterId, long machineId, final long startTimestamp) {
        if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
            throw new IllegalArgumentException(Log.formatMessage("dataCenterId can't be greater than {} or less than 0！", MAX_DATA_CENTER_NUM));
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException(Log.formatMessage("machineId can't be greater than {} or less than 0！", MAX_MACHINE_NUM));
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
        START_TIMESTAMP = startTimestamp;
    }

    /**
     * <dt>Description:nextId</dt>
     * <dd>产生下一个ID</dd>
     * <dd>author: wangnaifei@163.com</dd>
     * <dd>version:1.0 <i>2020/11/20 14:34</i></dd>
     *
     * @return long
     * @since jdk1.7+
     */
    public synchronized long nextId() {
        long currTimeStamp = getNewTimeStamp();
        if (currTimeStamp < lastTimeStamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currTimeStamp == lastTimeStamp) {
            //相同毫秒内，序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currTimeStamp = getNextMill();
            }
        } else {
            //不同毫秒内，序列号置为0
            sequence = 0L;
        }

        lastTimeStamp = currTimeStamp;

        return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT  //时间戳部分
                | dataCenterId << DATA_CENTER_LEFT                  //数据中心部分
                | machineId << MACHINE_LEFT                         //机器标识部分
                | sequence;                                         //序列号部分
    }

    public static void main(String[] args) {
        SnowFlakeID snowFlake = new SnowFlakeID(2, 3, Calendar.getInstance().getTimeInMillis());
        for (int i = 0; i < (1 << 4); i++) {
            //10进制
            System.out.println(snowFlake.nextId());
        }
    }
}
